var HxFlowUtil = {
    showNodeUser: function (users) {
        if (users && users.name && users.name.length > 0) {
            var ns = [];
            for (var i = 0, j = users.name.length; i < j && i < 5; i++) {
                ns.push(users.name[i]);
            }

            var result = ns.join(",");
            if (ns.length < users.name.length) {
                result += "等" + users.name.length + "项";
            }

            return result;
        }

        return "";
    }
};
var HxFlow = function (options) {
    this.options = options || {};

    var FlowModel = function (params, flow) {
        this.params = params || {};
        this.flow = flow;
        this.params.options = $.extend(
            {
                DragOptions: { cursor: 'pointer', zIndex: 2000 },
                ConnectionOverlays: [
                    ["Arrow", {
                        location: 1,
                        visible: true,
                        width: 11,
                        length: 11,
                        id: "ARROW"
                    }],
                    ["Label", {
                        location: 0.5,
                        id: "label",
                        cssClass: "link-text"
                    }]
                ],
                Container: "canvas"
            }, this.params.options);

        this.params.connectorStyle = $.extend({
            strokeWidth: 2,
            stroke: "#61B7CF",
            joinstyle: "round",
            outlineStroke: "white",
            outlineWidth: 1
        }, this.params.connectorStyle);

        this.params.connectorHoverStyle = $.extend({
            strokeWidth: 2,
            stroke: "#216477",
            outlineWidth: 0,
            outlineStroke: "white"
        }, this.params.connectorHoverStyle);

        this.params.endpointHoverStyle = $.extend({
            fill: "#216477",
            stroke: "#216477"
        }, this.params.endpointHoverStyle);

        this.endpoint = $.extend({
            endpoint: "Dot",
            paintStyle: {
                stroke: "#7AB02C",
                fill: "transparent",
                radius: 2,
                strokeWidth: 1
            },
            isSource: true,
            isTarget: true,
            connector: ["Flowchart", { stub: [30, 30], gap: 1, cornerRadius: 0, alwaysRespectStubs: true }],
            connectorStyle: this.params.connectorStyle,
            hoverPaintStyle: this.params.endpointHoverStyle,
            connectorHoverStyle: this.params.connectorHoverStyle,
            maxConnections: 1,
            dragOptions: {}
        }, this.params.endpoint);

        this.readonly = !!this.params.readonly;
        this.propPanel = $("#" + (this.params.propPanelId || "flow-props"));
        if (this.propPanel.length == 0) {
            this.propPanel = null;
        }
        else {
            this.propPanel.addClass("flow-prop-panel");
            var pc = this.propPanel.find(".flow-prop-list");
            if (pc.length == 0) {
                this.propPanel.append("<fieldset class=\"flow-prop-set\"><legend>属性</legend><div class=\"flow-prop-list\"></div></fieldset>");
            }

            pc = this.propPanel.find(".flow-data-list");
            if (pc.length == 0) {
                this.propPanel.append("<fieldset class=\"flow-data-set\" style=\"display:none\"><legend>数据</legend><div class=\"flow-data-list\"></div><div class=\"add-flow-data\"><i class=\"fa fa-plus\"></i></div></fieldset>");
            }

            var that = this;
            this.propPanel.on("click", ".add-flow-data", function () {
                if (that.readonly) {
                    return;
                }
                $(this).prev().append("<div class=\"flow-data\"><div class=\"flow-data-label\"><input type=\"text\" class=\"data\" placeholder=\"标签\"/></div><div class=\"flow-data-name\"><input type=\"text\" class=\"data\" placeholder=\"名称\"/></div><div class=\"flow-data-value\"><input type=\"text\" class=\"data\" placeholder=\"值\"/></div><div class=\"flow-data-remove\"><i class=\"fa fa-remove\" title=\"移除\"></i></div></div>");
            });
            this.propPanel.on("input", "input.data", function () {
                var ps = $(this).closest(".flow-prop-panel");
                var node = ps.data("obj");
                if (node) {
                    node.data = [];
                    ps.find(".flow-data").each(function (i, o) {
                        var d = {
                            label: $(o).find(".flow-data-label input").val(),
                            name: $(o).find(".flow-data-name input").val(),
                            value: $(o).find(".flow-data-value input").val()
                        };

                        if (d.name) {
                            node.data.push(d);
                        }
                    });
                }
            });
            this.propPanel.on("click", ".flow-model-key", function () {
                var ps = $(this).closest(".flow-prop-panel");
                var node = ps.data("obj");
                if (node) {
                    if (!node.props) {
                        node.props = {};
                    }

                    var mk = node.props.modelKey;
                    if (!mk) {
                        mk = that.flow.panel.el.data("obj").props.modelKey;
                    }

                    if (!mk) {
                        mk = "";
                    }

                    $.modalOpen({
                        id: "FlowModelKey",
                        title: "页面组件" + mk + "设置",
                        url: "/WorkFlow/WorkFlow/ModelKey?type=" + mk,
                        width: "700px",
                        height: "600px",
                        success: function (layero, index) {
                            var frame = top.frames["FlowModelKey"];
                            if (node.props.modelKeys && node.props.modelKeys.length > 0) {
                                node.props.modelKeys.forEach(function (k) {
                                    frame.init(k);
                                });
                            }
                        },
                        callBack: function (iframeId, index) {
                            try {
                                if (!that.readonly) {
                                    node.props.modelKeys = top.frames[iframeId].getKeys();
                                }
                                top.layer.close(index);
                            }
                            catch (err) {
                                $.modalAlert(err.message, "error");
                                return false;
                            }
                        }
                    });
                }
            });
            this.propPanel.on("click", ".flow-users", function () {
                var ps = $(this).closest(".flow-prop-panel");
                var node = ps.data("obj");
                if (node) {
                    if (!node.props) {
                        node.props = {};
                    }

                    var btn = $(this);

                    $.modalOpen({
                        id: "FlowUsers",
                        title: "人员筛选",
                        url: "/WorkFlow/WorkFlow/NodeUser",
                        width: "700px",
                        height: "600px",
                        success: function (layero, index) {
                            var frame = top.frames["FlowUsers"];
                            if (node.props.users) {
                                frame.init(node.props.users);
                            }
                        },
                        callBack: function (iframeId, index) {
                            try {
                                if (!that.readonly) {
                                    node.props.users = top.frames[iframeId].getUsers();
                                    btn.prev("input").val(HxFlowUtil.showNodeUser(node.props.users));
                                }
                                top.layer.close(index);
                            }
                            catch (err) {
                                $.modalAlert(err.message, "error");
                                return false;
                            }
                        }
                    });
                }
            });
            this.propPanel.on("click", ".flow-cusers", function () {
                var ps = $(this).closest(".flow-prop-panel");
                var node = ps.data("obj");
                if (node) {
                    if (!node.props) {
                        node.props = {};
                    }
                    var btn = $(this);
                    $.modalOpen({
                        id: "FlowCUsers",
                        title: "候选人员",
                        url: "/WorkFlow/WorkFlow/NodeUser",
                        width: "700px",
                        height: "600px",
                        success: function (layero, index) {
                            var frame = top.frames["FlowCUsers"];
                            if (node.props.cusers) {
                                frame.init(node.props.cusers);
                            }
                        },
                        callBack: function (iframeId, index) {
                            try {
                                if (!that.readonly) {
                                    node.props.cusers = top.frames[iframeId].getUsers();
                                    btn.prev("input").val(HxFlowUtil.showNodeUser(node.props.cusers));
                                }
                                top.layer.close(index);
                            }
                            catch (err) {
                                $.modalAlert(err.message, "error");
                                return false;
                            }
                        }
                    });
                }
            });
            this.propPanel.on("click", ".flow-condition", function () {
                var ps = $(this).closest(".flow-prop-panel");
                var node = ps.data("obj");
                if (node) {
                    if (!node.props) {
                        node.props = {};
                    }

                    var btn = $(this);
                    $.modalOpen({
                        id: "FlowCondition",
                        title: "流转条件",
                        url: "/WorkFlow/WorkFlow/FlowCondition",
                        width: "700px",
                        height: "600px",
                        success: function (layero, index) {
                            var frame = top.frames["FlowCondition"];
                            frame.init(node.props.condition);
                        },
                        callBack: function (iframeId, index) {
                            try {
                                if (!that.readonly) {
                                    node.props.condition = top.frames[iframeId].getCondition();
                                    btn.parent().find("textarea").val(node.props.condition);
                                }
                                top.layer.close(index);
                            }
                            catch (err) {
                                $.modalAlert(err.message, "error");
                                return false;
                            }
                        }
                    });
                }
            });
            this.propPanel.on("input", "input.flow-prop-input,textarea.flow-prop-text", function () {
                var ps = $(this).closest(".flow-prop-panel");
                var node = ps.data("obj");
                if (node) {
                    if (!node.props) {
                        node.props = {};
                    }

                    if (node.getOverlay && $(this).hasClass("overlay-label")) {
                        node.getOverlay("label").setLabel($(this).val());
                    }
                    else {
                        var commentId = $(this).attr("comment-id");
                        if (commentId) {
                            var first = $($("#" + commentId).children()[0]);
                            if (first.is("label")) {
                                first.text($(this).val());
                            }
                            else {
                                first.val($(this).val());
                            }
                        }
                        else {
                            node.props[$(this).data("prop")] = $(this).val();
                        }
                    }
                }
            });
            this.propPanel.on("change", "input.flow-prop-flag", function () {
                var ps = $(this).closest(".flow-prop-panel");
                var node = ps.data("obj");
                if (node) {
                    if (!node.props) {
                        node.props = {};
                    }
                    var p = $(this).data("prop"), ck = $(this).prop("checked");
                    node.props[p] = ck;
                    if (p == "isAudit") {
                        $("input[data-prop='auditUrl']").prop("disabled", !ck);
                    }
                }
            });
            this.propPanel.on("change", "select.flow-prop-select", function () {
                var ps = $(this).closest(".flow-prop-panel");
                var node = ps.data("obj");
                if (node) {
                    if (!node.props) {
                        node.props = {};
                    }
                    node.props[$(this).data("prop")] = $(this).val();
                }
            });
        }
        this.instance = jsPlumb.getInstance(this.params.options);
        this._init();
    }

    FlowModel.prototype._init = function () {
        this.params.basicType = $.extend({
            connector: "StateMachine",
            paintStyle: { stroke: "red", strokeWidth: 2 },
            hoverPaintStyle: { stroke: "blue" }
        }, this.params.basicType);
        this.instance.registerConnectionType("basic", this.params.basicType);

        var that = this;
        this.instance.batch(function () {
            that.instance.bind("click", function (conn, originalEvent) {
                if (that.onSelect) {
                    that.onSelect(conn);
                }
                conn.addType("basic");
            });

            that.instance.bind("beforeDrop", function (info) {
                if (that.readonly) {
                    return false;
                }

                if ($("#" + info.sourceId).is(".flow-node.comment")) {
                    return false;
                }

                var targetIsComment = $("#" + info.targetId).is(".flow-node.comment");
                var flag = true;
                that.instance.getAllConnections().forEach(function (c) {
                    if (c != info.connection && c.sourceId == info.sourceId && ((c.targetId == info.targetId)
                        || (targetIsComment && $("#" + c.targetId).is(".flow-node.comment")))) {
                        flag = false;
                        return false;
                    }
                });
                return flag;
            });
        });
        return this;
    }

    FlowModel.prototype.getAllConnections = function () {
        return this.instance.getAllConnections();
    }

    FlowModel.prototype.draggable = function (id) {
        this.instance.draggable(id);
        return this;
    }

    FlowModel.prototype.detach = function (conn) {
        this.instance.detach(conn);
        return this;
    }

    FlowModel.prototype.repaintEverything = function () {
        this.instance.repaintEverything();
        return this;
    }

    FlowModel.prototype.addEndpoints = function (toId, sourceAnchors) {
        //Circle(圆) Ellipse(椭圆) Triangle(三角形) Diamond(菱形) Rectangle(矩形) Square(正方形)
        for (var i = 0; i < sourceAnchors.length; i++) {
            var targetUuid = toId + sourceAnchors[i];
            this.instance.addEndpoint(toId,
                this.endpoint, {
                anchor: sourceAnchors[i], uuid: targetUuid
            });
        }
        return this;
    }

    FlowModel.prototype.addLinks = function (links) {
        if (!links || links.length == 0) {
            return this;
        }

        var that = this;
        links.forEach(function (nl) {
            if (nl.startPosition && nl.endPosition) {
                var sps = nl.startPosition.split(",");
                var eps = nl.endPosition.split(",");
                var conn = that.instance.connect({ uuids: [nl.startNode + sps[2], nl.endNode + eps[2]] });
                if (nl.text) {
                    conn.getOverlay("label").setLabel(nl.text);
                }
                conn.props = {};
                if (nl.condition) {
                    conn.props.condition = nl.condition;
                }
            }
        });
        return this;
    }

    var FlowShapes = function (shapeSelector, flow) {
        this.shapeSelector = shapeSelector || ".flow-shape";
        this.flow = flow;
        this._init();
    };

    FlowShapes.prototype._init = function () {
        $(this.shapeSelector).draggable({
            helper: "clone",
            scope: "flow"
        });
        return this;
    };

    FlowShapes.prototype.showProp = function (node) {
        if (this.flow.model.propPanel) {
            var type = node.data("type");
            var pp = this.flow.model.propPanel.find(".flow-prop-list").empty();
            var data = node.data("obj");
            this.flow.model.propPanel.data("obj", data);
            if (!data) {
                return;
            }
            if (!data.props) {
                data.props = {};
            }
            var p = null, showData = true;
            switch (type) {
                case "start":
                case "end":
                    p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-info-circle\" title=\"节点处理前触发\"></i>开始事件</span><input type=\"text\" data-prop=\"beforeEvent\" class=\"flow-prop-input\"/></div>");
                    p.find("input").val(data.props.beforeEvent);
                    pp.append(p);
                    p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-info-circle\" title=\"节点处理后触发\"></i>结束事件</span><input type=\"text\" data-prop=\"afterEvent\" class=\"flow-prop-input\"/></div>");
                    p.find("input").val(data.props.afterEvent);
                    pp.append(p);
                    break;
                case "task":
                    p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-exclamation-triangle\" title=\"单个流程节点编码唯一\"></i>节点编码</span><input type=\"text\" data-prop=\"code\" class=\"flow-prop-input\"/></div>");
                    p.find("input").val(data.props.code);
                    pp.append(p);
                    p = $("<div class=\"flow-prop\"><span>节点类型</span><input id=\"ckIsTask\" type=\"checkbox\" data-prop=\"isTask\" class=\"flow-prop-flag\"/><label for=\"ckIsTask\">任务</label><input id=\"ckIsAudit\" type=\"checkbox\" data-prop=\"isAudit\" class=\"flow-prop-flag\"/><label for=\"ckIsAudit\">审核</label><input id=\"ckIsCSign\" type=\"checkbox\" data-prop=\"isCSign\" class=\"flow-prop-flag\"/><label for=\"ckIsCSign\">会签</label></div>");
                    if (data.props.isTask === undefined || data.props.isTask === null) {
                        data.props.isTask = true;
                    }
                    p.find("#ckIsTask").prop("checked", data.props.isTask);
                    p.find("#ckIsAudit").prop("checked", data.props.isAudit);
                    p.find("#ckIsCSign").prop("checked", data.props.isCSign);
                    pp.append(p);
                    p = $("<div class=\"flow-prop\"><span>指定人员</span><input type=\"text\" data-prop=\"users\" class=\"flow-prop-input\" readonly=\"readonly\"/><div class=\"flow-users\"><i class=\"fa fa-gear\" title=\"设置\"></i></div></div>");
                    if (data.props.users) {
                        p.find("input").val(HxFlowUtil.showNodeUser(data.props.users));
                    }
                    pp.append(p);
                    p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-exclamation-triangle\" title=\"指定候选人，节点操作成功会弹出选择处理操作人界面\"></i>候选人</span><input type=\"text\" data-prop=\"cusers\" class=\"flow-prop-input\" readonly=\"readonly\"/><div class=\"flow-cusers\"><i class=\"fa fa-gear\" title=\"设置\"></i></div></div>");
                    if (data.props.cusers) {
                        p.find("input").val(HxFlowUtil.showNodeUser(data.props.cusers));
                    }
                    pp.append(p);
                    p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-exclamation-triangle\" title=\"覆盖流程定义中设置项\"></i>业务逻辑页面</span><input type=\"text\" data-prop=\"businessUrl\" class=\"flow-prop-input\"/></div>");
                    p.find("input").val(data.props.businessUrl);
                    pp.append(p);
                    p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-exclamation-triangle\" title=\"覆盖流程定义中设置项\"></i>业务逻辑类</span><input type=\"text\" data-prop=\"modelKey\" class=\"flow-prop-input\"/><div class=\"flow-model-key\"><i class=\"fa fa-gear\" title=\"设置\"></i></div></div>");
                    p.find("input").val(data.props.modelKey);
                    pp.append(p);
                    p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-exclamation-triangle\" title=\"覆盖流程定义中设置项\"></i>审核页面</span><input type=\"text\" data-prop=\"auditUrl\" class=\"flow-prop-input\"/></div>");
                    p.find("input").val(data.props.auditUrl).prop("disabled", !data.props.isAudit);
                    pp.append(p);
                    p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-info-circle\" title=\"节点处理前触发\"></i>开始事件</span><input type=\"text\" data-prop=\"beforeEvent\" class=\"flow-prop-input\"/></div>");
                    p.find("input").val(data.props.beforeEvent);
                    pp.append(p);
                    p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-info-circle\" title=\"节点处理后触发\"></i>结束事件</span><input type=\"text\" data-prop=\"afterEvent\" class=\"flow-prop-input\"/></div>");
                    p.find("input").val(data.props.afterEvent);
                    pp.append(p);
                    break;
                case "comment":
                    showData = false;
                    p = $("<div class=\"flow-prop\"><span>说明</span><br /><textarea data-prop=\"comment\" class=\"flow-prop-text\" style=\"height:100px;\"></textarea></div>");
                    var first = $(node.children()[0]);
                    if (first.is("label")) {
                        p.find("textarea").attr("comment-id", node.attr("id")).val(first.text());
                    }
                    else {
                        //textarea
                        p.find("textarea").attr("comment-id", node.attr("id")).val(first.val());
                    }
                    pp.append(p);
                    break;
            }

            var dp = this.flow.model.propPanel.find(".flow-data-list").empty();
            this.flow.model.propPanel.find(".flow-data-set").hide();
            if (showData) {
                this.flow.model.propPanel.find(".flow-data-set").show();
                if (data.data && data.data.length > 0) {
                    data.data.forEach(function (d) {
                        var dc = $("<div class=\"flow-data\"><div class=\"flow-data-label\"><input type=\"text\" class=\"data\" placeholder=\"标签\"/></div><div class=\"flow-data-name\"><input type=\"text\" class=\"data\" placeholder=\"名称\"/></div><div class=\"flow-data-value\"><input type=\"text\" class=\"data\" placeholder=\"值\"/></div><div class=\"flow-data-remove\"><i class=\"fa fa-remove\" title=\"移除\"></i></div></div>");
                        dp.append(dc);
                        dc.find(".flow-data-label input").val(d.label);
                        dc.find(".flow-data-name input").val(d.name);
                        dc.find(".flow-data-value input").val(d.value);
                    });
                }
            }
            if (this.flow.model.readonly) {
                this.flow.model.propPanel.find("input,textarea").prop("readonly", true);
                this.flow.model.propPanel.find("select,:checkbox,:radio").prop("disabled", true);
            }
        }
        return this;
    };

    var FlowPanel = function (options, flow) {
        this.options = options || {};
        this.options.id = this.options.id || "flow-center";
        this.options.selectId = this.options.selectId || "flow-select";
        this.flow = flow;
        this.el = $("#" + this.options.id);
        if (this.el.length == 0) {
            throw new Error("流程面板ID无效");
        }
        var data = this.el.data("obj");
        if (!data) {
            data = {
                props: {},
                data: []
            };
            this.el.data("obj", data);
        }
        this.selectArea = $("<div>").attr("id", this.options.selectId).addClass("flow-select");
        this.el.append(this.selectArea);

        var parent = this.el.parent();
        this.parentWidth = parseInt(parent.width());
        this.parentHeight = parseInt(parent.height());

        this.nodeAnchors = ["TopLeft", "TopCenter", "TopRight", "LeftMiddle", "RightMiddle", "BottomLeft", "BottomCenter", "BottomRight"];

        this._init();
    }

    FlowPanel.prototype._init = function () {
        var that = this;
        this.el.focus();
        this.el.on("input", "textarea", function () {
            var cid = $(this).parent().attr("id");
            if (that.flow.model.propPanel) {
                that.flow.model.propPanel.find("textarea[comment-id='" + cid + "']").val($(this).val());
            }
        });
        this.flow.model.onSelect = function (conn) {
            that.clearSelection();
            that.showConnectionProp(conn);
        };
        this.el.droppable({
            scope: "flow",
            drop: function (event, ui) {
                if (!that.flow.model.readonly) {
                    that.createNode(event, ui);
                }
            }
        });
        this.el.on("keydown", function () {
            var evt = window.event || arguments[0];
            var key = evt.keyCode || evt.which;
            if (that.flow.model.readonly) {
                return;
            }

            var move = null;
            if (key == 46) {
                var ids = [], repaint = false;
                $(this).find(".flow-node.selected").each(function (i, n) {
                    ids.push($(n).attr("id"));
                    $(n).remove();
                });
                if (ids.length > 0) {
                    that.flow.model.getAllConnections().forEach(function (c) {
                        if (ids.indexOf(c.sourceId) > -1 || ids.indexOf(c.targetId) > -1) {
                            that.flow.model.detach(c);
                        }
                    });
                    repaint = true;
                }

                that.flow.model.getAllConnections().forEach(function (c) {
                    if ($(c.connector.path).attr("stroke") == "red") {
                        that.flow.model.detach(c);
                        repaint = true;
                    }
                });

                if (repaint) {
                    that.flow.model.repaintEverything();
                }
            }
            else if (key == 37) {
                move = { dc: "left", size: -1 };
            }
            else if (key == 38) {
                move = { dc: "top", size: -1 };
            }
            else if (key == 39) {
                move = { dc: "left", size: 1 };
            }
            else if (key == 40) {
                move = { dc: "top", size: 1 };
            }

            if (move) {
                var nodes = $(this).find(".flow-node.selected");
                nodes.each(function (i, n) {
                    $(n).css(move.dc, parseInt($(n).css(move.dc), 10) + move.size);
                });
                if (nodes.length) {
                    that.flow.model.repaintEverything();
                }
            }
        }).on("mousedown", function () {
            var evt = window.event || arguments[0];
            var buttonType = evt.buttons || evt.button;
            if (evt.target != undefined && evt.target.id !== that.options.id) {
                return;
            }
            if (evt.srcElement != undefined && evt.srcElement.id !== that.options.id) {
                return;
            }
            if (evt.buttons == undefined && buttonType == 0) {
                that.beforeSelect(evt);
            }
            if (buttonType === 1) {
                that.beforeSelect(evt);
            }
            if (buttonType === 2) {
                var offset = that.el.offset();
                that.mousePos = { x: evt.pageX, y: evt.pageY, left: offset.left, top: offset.top };
                that.el.css("cursor", "move");
            }
        }).on("mousemove", function () {
            var evt = window.event || arguments[0];
            var buttonType = evt.buttons || evt.button;
            if (evt.buttons == undefined && buttonType == 0) {
                that.onSelect(evt);
            }
            if (buttonType === 1) {
                that.onSelect(evt);
            }

            if (buttonType === 2 && that.mousePos) {
                var x = that.mousePos.left + evt.pageX - that.mousePos.x;
                var y = that.mousePos.top + evt.pageY - that.mousePos.y;
                if (x >= 0) {
                    x = 0;
                }
                if (y >= 0) {
                    y = 0;
                }
                that.selectArea.css({ left: x, top: y, width: that.parentWidth - x, height: that.parentHeight - y });
            }
        }).on("mouseup", function () {
            that.el.css("cursor", "default");
            that.selectArea.hide();
            that.selectPos = null;
        });
        return this;
    }

    FlowPanel.prototype.showConnectionProp = function (conn) {
        if (this.flow.model.propPanel) {
            this.flow.model.propPanel.data("obj", conn);
            if (!conn.props) {
                conn.props = {};
            }
            var pp = this.flow.model.propPanel.find(".flow-prop-list").empty();
            var p = $("<div class=\"flow-prop\"><span>标签</span><input type=\"text\" data-prop=\"text\" class=\"flow-prop-input overlay-label\"/></div>");
            p.find("input").val(conn.getOverlay("label").getLabel());
            pp.append(p);
            p = $("<div class=\"flow-prop\"><span>流转条件</span><i class=\"flow-condition fa fa-gear\" title=\"设置条件\">设置</i><br /><textarea data-prop=\"condition\" class=\"flow-prop-text\" style=\"height:100px;\"></textarea></div>");
            p.find("textarea").val(conn.props.condition);
            pp.append(p);
            this.flow.model.propPanel.find(".flow-data-set").hide();
            this.flow.model.propPanel.find(".flow-data-list").empty();
            if (this.flow.model.readonly) {
                this.flow.model.propPanel.find("input,textarea").prop("readonly", true);
                this.flow.model.propPanel.find("select,:checkbox,:radio").prop("disabled", true);
            }
        }
        return this;
    }

    FlowPanel.prototype.showProp = function () {
        if (this.flow.model.propPanel) {
            var data = this.el.data("obj");
            if (!data.props) {
                data.props = {};
            }
            this.flow.model.propPanel.data("obj", data);
            var pp = this.flow.model.propPanel.find(".flow-prop-list").empty();
            var p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-info-circle\" title=\"交互页面宽高\"></i>交互页面大小</span><input type=\"text\" data-prop=\"size\" class=\"flow-prop-input\" placeholder=\"900,700\"/></div>");
            p.find("input").val(data.props.size);
            pp.append(p);
            p = $("<div class=\"flow-prop\"><span>业务逻辑页面</span><input type=\"text\" data-prop=\"businessUrl\" class=\"flow-prop-input\"/></div>");
            p.find("input").val(data.props.businessUrl);
            pp.append(p);
            p = $("<div class=\"flow-prop\"><span>业务逻辑类</span><input type=\"text\" data-prop=\"modelKey\" class=\"flow-prop-input\"/></div>");
            p.find("input").val(data.props.modelKey);
            pp.append(p);
            p = $("<div class=\"flow-prop\"><span>审核页面</span><input type=\"text\" data-prop=\"auditUrl\" class=\"flow-prop-input\"/></div>");
            p.find("input").val(data.props.auditUrl);
            pp.append(p);
            p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-info-circle\" title=\"流程开始前触发\"></i>开始事件</span><input type=\"text\" data-prop=\"beforeEvent\" class=\"flow-prop-input\"/></div>");
            p.find("input").val(data.props.beforeEvent);
            pp.append(p);
            p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-info-circle\" title=\"流程结束时触发\"></i>结束事件</span><input type=\"text\" data-prop=\"afterEvent\" class=\"flow-prop-input\"/></div>");
            p.find("input").val(data.props.afterEvent);
            pp.append(p);
            p = $("<div class=\"flow-prop\"><span><i class=\"fa fa-info-circle\" title=\"未匹配人员自动跳过节点时触发\"></i>跳过事件</span><input type=\"text\" data-prop=\"skipEvent\" class=\"flow-prop-input\"/></div>");
            p.find("input").val(data.props.skipEvent);
            pp.append(p);
            this.flow.model.propPanel.find(".flow-data-set").hide();
            this.flow.model.propPanel.find(".flow-data-list").empty();
        }
        return this;
    }

    FlowPanel.prototype.beforeSelect = function (evt) {
        this.clearSelection();
        var offset = this.el.offset();
        this.selectPos = { x: evt.pageX - offset.left, y: evt.pageY - offset.top };
    }

    FlowPanel.prototype.onSelect = function (evt) {
        if (!this.selectPos) return;
        var offset = this.el.offset();
        var pos = { x: evt.pageX - offset.left, y: evt.pageY - offset.top };
        this.selectArea.css({
            left: Math.min(pos.x, this.selectPos.x) + "px",
            top: Math.min(pos.y, this.selectPos.y) + "px",
            width: Math.abs(pos.x - this.selectPos.x) + "px",
            height: Math.abs(pos.y - this.selectPos.y) + "px"
        });

        var sEle = this.selectArea[0];
        this.el.find(".flow-node").each(function (i, n) {
            if ($.intersectWith(sEle, n)) {
                $(n).addClass("selected");
            }
            else {
                $(n).removeClass("selected");
            }
        });
        this.selectArea.show();
    }

    FlowPanel.prototype.select = function (node) {
        this.clearSelection();
        node.addClass("selected");
        this.flow.shape.showProp(node);
        return this;
    }

    FlowPanel.prototype.clearSelection = function () {
        this.flow.model.getAllConnections().forEach(function (c) {
            c.removeType("basic");
        });
        this.el.find(".flow-node").removeClass("selected");
        this.showProp();
        return this;
    }

    FlowPanel.prototype.startMove = function () {
        this.el.find(".flow-node.selected").each(function (i, n) {
            var pos = $(n).position();
            $(n).attr("x-left", pos.left).attr("x-top", pos.top);
        });
        return this;
    }

    FlowPanel.prototype.moveSelectNode = function (event, ui) {
        var left = ui.position.left - ui.originalPosition.left;
        var top = ui.position.top - ui.originalPosition.top;
        this.el.find(".flow-node.selected").each(function (i, n) {
            $(n).css({
                left: parseInt($(n).attr("x-left")) + left, top: parseInt($(n).attr("x-top")) + top
            });
        });
        return this;
    }

    FlowPanel.prototype.createNode = function (event, ui) {
        var type = ui.helper.data("type");
        if ((type == "start" && this.el.find(".flow-node.start").length > 0)
            || (type == "end" && this.el.find(".flow-node.end").length > 0)) {
            return;
        }

        var id = jsPlumbUtil.uuid();
        var node = $("<div>").addClass("flow-node").addClass(ui.helper.data("shape"))
            .attr("id", id).data("type", type).html(ui.helper.html());

        node.data("obj", {});
        this.el.append(node);

        var offset = this.el.offset();
        var left = ui.offset.left - offset.left + parseInt(ui.helper.css("marginLeft"));
        var top = ui.offset.top - offset.top + parseInt(ui.helper.css("marginTop"));
        node.css({ top: top, left: left });

        var that = this;
        node.on("dblclick", function () {
            var lbl = $(this).find("label");
            if (lbl.length > 0) {
                lbl.replaceWith($("<textarea>").val(lbl.text()));
            }
        }).on("mousedown", function () {
            var evt = window.event || arguments[0];
            if (evt.shiftKey || evt.ctrlKey) {
                $(this).toggleClass("selected");
                jsPlumbUtil.consume(evt, false);
                that.flow.shape.showProp($(this));
            }
            else {
                that.select($(this));
            }
        });

        this.flow.model.addEndpoints(id, this.nodeAnchors);

        node.draggable({
            containment: "parent",
            start: function () {
                that.startMove();
            },
            drag: function (event, ui) {
                that.moveSelectNode(event, ui);
                that.flow.model.repaintEverything();
            },
            stop: function () {
                that.flow.model.repaintEverything();
            }
        });
        node.resizable({
            resize: function () {
                that.flow.model.repaintEverything();
            },
            stop: function () {
                that.flow.model.repaintEverything();
            }
        });
        this.select(node);
        return this;
    }

    FlowPanel.prototype.addNodes = function (nodes) {
        if (!nodes || nodes.length == 0) {
            return this;
        }

        var that = this;
        nodes.forEach(function (n) {
            var type = n.type == 1 ? "start" : (n.type == 2 ? "end" : (n.type == 0 ? "comment" : "task"));
            var node = $("<div>").addClass("flow-node").addClass(type)
                .attr("id", n.id).data("type", type);

            var label = $("<label></label>");
            if (n.type == 0) {
                label.text(n.remark);
            }
            else {
                label.text(n.name);
            }

            node.append(label);
            var obj = {
                props: {},
                data: []
            };

            if (type == "task") {
                obj.props.code = n.code;
                obj.props.isTask = (n.type & 4) == 4;
                obj.props.isAudit = (n.type & 8) == 8;
                obj.props.isCSign = (n.type & 16) == 16;
                obj.props.businessUrl = n.businessUrl;
                obj.props.modelKey = n.modelKey;
                obj.props.auditUrl = n.auditUrl;
                obj.props.users = { name: [], ids: [] };
                obj.props.cusers = { name: [], ids: [] };

                if (n.users && n.users.length > 0) {
                    n.users.forEach(function (u) {
                        if (u.isCandidate) {
                            obj.props.cusers.name.push(u.userName);
                            obj.props.cusers.ids.push((u.userType == 1 ? "u" : (u.userType == 2 ? "r" : (u.userType == 3 ? "d" : "p"))) + "_" + u.userId);
                        }
                        else {
                            obj.props.users.name.push(u.userName);
                            obj.props.users.ids.push((u.userType == 1 ? "u" : (u.userType == 2 ? "r" : (u.userType == 3 ? "d" : "p"))) + "_" + u.userId);
                        }
                    });
                }
            }

            if (type != "comment") {
                obj.props.beforeEvent = n.before;
                obj.props.afterEvent = n.after;
            }

            if (n.properties) {
                n.properties.forEach(function (p) {
                    //属性
                    if (p.type == 0 && p.key == "modelKeys" && type == "task") {
                        obj.props.modelKeys = JSON.parse(p.value);
                    }

                    //数据
                    if (p.type == 1) {
                        obj.data.push({ label: p.name, name: p.key, value: p.value });
                    }
                });
            }
            node.data("obj", obj);

            that.el.append(node);
            if (n.rectangle) {
                var rs = n.rectangle.split(",");
                node.css({ left: parseInt(rs[0]), top: parseInt(rs[1]), width: parseFloat(rs[2]), height: parseFloat(rs[3]) });
            }

            if (!that.flow.model.readonly) {
                node.on("dblclick", function () {
                    var lbl = $(this).find("label");
                    if (lbl.length > 0) {
                        lbl.replaceWith($("<textarea>").val(lbl.text()));
                    }
                });

                node.draggable({
                    containment: "parent",
                    start: function () {
                        that.startMove();
                    },
                    drag: function (event, ui) {
                        that.moveSelectNode(event, ui);
                        that.flow.model.repaintEverything();
                    },
                    stop: function () {
                        that.flow.model.repaintEverything();
                    }
                });
                node.resizable({
                    resize: function () {
                        that.flow.model.repaintEverything();
                    },
                    stop: function () {
                        that.flow.model.repaintEverything();
                    }
                });
            }
            node.on("mousedown", function () {
                var evt = window.event || arguments[0];
                if (evt.shiftKey || evt.ctrlKey) {
                    $(this).toggleClass("selected");
                    jsPlumbUtil.consume(evt, false);
                    that.flow.shape.showProp($(this));
                }
                else {
                    that.select($(this));
                }
            });
            that.flow.model.addEndpoints(n.id, that.nodeAnchors);
        });
    }

    this.model = new FlowModel(this.options.model, this);
    this.shape = new FlowShapes(this.options.shapeSelector, this);
    this.panel = new FlowPanel(this.options.panel, this);
}

HxFlow.prototype.selectNode = function (node) {
    this.panel.select(node);
}

HxFlow.prototype.clear = function () {
    this.panel.el.find(".flow-node").remove();
    var that = this, cs = this.model.getAllConnections();
    cs.forEach(function (c) {
        that.model.detach(c);
    });
    if (cs.length > 0) {
        this.model.repaintEverything();
    }
}

HxFlow.prototype.setFlow = function (flow) {
    if (!flow) {
        return;
    }
    var data = this.panel.el.data("obj");
    if (!data.props) {
        data.props = {};
    }
    data.props.size = flow.size;
    data.props.businessUrl = flow.businessUrl;
    data.props.modelKey = flow.modelKey;
    data.props.auditUrl = flow.auditUrl;
    data.props.beforeEvent = flow.before;
    data.props.afterEvent = flow.after;
    data.props.skipEvent = flow.skipEvent;
    this.panel.showProp();
    if (flow.nodes && flow.nodes.length > 0) {
        this.clear();
        this.panel.addNodes(flow.nodes);
        this.model.addLinks(flow.links);
    }
}

HxFlow.prototype.getFlow = function () {
    var that = this;
    var data = this.panel.el.data("obj");
    if (!data.props) {
        data.props = {};
    }
    var flow = {
        businessUrl : data.props.businessUrl,
        modelKey: data.props.modelKey,
        auditUrl:data.props.auditUrl,
        before: data.props.beforeEvent,
        after: data.props.afterEvent,
        skipEvent: data.props.skipEvent,
        size: data.props.size,
        nodes: [],
        links:[]
    };

    if (flow.size) {
        var ss = flow.size.split(",");
        if (ss.length < 2
            || !ss[0]
            || isNaN(ss[0])
            || !ss[1]
            || isNaN(ss[1])) {
            throw new Error("交互页面大小输入不合法");
        }
    }
    var sids = [], eids = [];
    this.model.getAllConnections().forEach(function (c) {
        var eps = [c.endpoints[0], c.endpoints[1]];
        var link = {
            text: c.getOverlay("label").getLabel(),
            startNode: c.sourceId,
            endNode: c.targetId,
            startPosition: String([eps[0].canvas.offsetLeft, eps[0].canvas.offsetTop, eps[0].anchor.type]),
            endPosition: String([eps[1].canvas.offsetLeft, eps[1].canvas.offsetTop, eps[1].anchor.type])
        };
        sids.push(link.startNode);
        eids.push(link.endNode);
        if (c.props && c.props.condition) {
            link.condition = c.props.condition;
        }
        flow.links.push(link);
    });

    var sflag = false, eflag = false;
    this.panel.el.find(".flow-node").each(function (i, n) {
        var ne = $(n), obj = ne.data("obj");
        var node = { id: ne.attr("id"), users: [], properties: [] };
        var lbl = $(ne.children()[0]);
        if (lbl.is("label")) {
            node.name = lbl.text();
        }
        else {
            //textarea
            node.name = lbl.val();
        }

        node.rectangle = String([parseInt(ne.css("left"), 10), parseInt(ne.css("top"), 10), ne.width(), ne.height()]);
        if (ne.hasClass("start")) {
            node.type = 1;
            sflag = true;
            if (sids.indexOf(node.id) == -1) {
                that.selectNode(ne);
                throw new Error("开始节点不能是孤立节点");
            }
            if (obj.props.beforeEvent) {
                node.before = obj.props.beforeEvent;
            }
            if (obj.props.afterEvent) {
                node.after = obj.props.afterEvent;
            }
        }
        else if (ne.hasClass("end")) {
            eflag = true;
            node.type = 2;
            if (eids.indexOf(node.id) == -1) {
                that.selectNode(ne);
                throw new Error("结束节点不能是孤立节点");
            }
            if (obj.props.beforeEvent) {
                node.before = obj.props.beforeEvent;
            }
            if (obj.props.afterEvent) {
                node.after = obj.props.afterEvent;
            }
        }
        else if (ne.hasClass("task")) {
            if (eids.indexOf(node.id) == -1) {
                that.selectNode(ne);
                throw new Error("流程中存在孤立节点");
            }

            if (!node.name) {
                that.selectNode(ne);
                throw new Error("节点名称不能为空");
            }
            node.type = 4;
            if (obj.props) {
                if (obj.props.code) {
                    node.code = obj.props.code;
                }
                if (!obj.props.isTask) {
                    node.type = 0;
                }
                if (obj.props.isAudit) {
                    node.type |= 8;
                }
                if (obj.props.isCSign) {
                    node.type |= 16;
                }
                if (node.type == 0) {
                    that.selectNode(ne);
                    throw new Error("节点类型无效");
                }
                if (obj.props.users && obj.props.users.name && obj.props.users.name.length > 0) {
                    for (var i = 0, j = obj.props.users.name.length; i < j; i++) {
                        var type = obj.props.users.ids[i].substr(0, 1), id = obj.props.users.ids[i].substring(2);
                        node.users.push({
                            userId: id,
                            userType: type == "u" ? 1 : (type == "r" ? 2 : (type == "d" ? 3 : 4)),
                            isCandidate: false,
                            userName: obj.props.users.name[i]
                        });
                    }
                }
                if (obj.props.cusers && obj.props.cusers.name && obj.props.cusers.name.length > 0) {
                    for (var i = 0, j = obj.props.cusers.name.length; i < j; i++) {
                        var type = obj.props.cusers.ids[i].substr(0, 1), id = obj.props.cusers.ids[i].substring(2);
                        node.users.push({
                            userId: id,
                            userType: type == "u" ? 1 : (type == "r" ? 2 : (type == "d" ? 3 : 4)),
                            isCandidate: true,
                            userName: obj.props.cusers.name[i]
                        });
                    }
                }
                if (obj.props.businessUrl) {
                    node.businessUrl = obj.props.businessUrl;
                }
                if (obj.props.modelKey) {
                    node.modelKey = obj.props.modelKey;
                }
                if (obj.props.modelKeys) {
                    node.properties.push({ name: "业务逻辑键设置", key: "modelKeys", value: JSON.stringify(obj.props.modelKeys), type: 0 });
                }
                if (obj.props.auditUrl) {
                    node.auditUrl = obj.props.auditUrl;
                }
                if (obj.props.beforeEvent) {
                    node.before = obj.props.beforeEvent;
                }
                if (obj.props.afterEvent) {
                    node.after = obj.props.afterEvent;
                }
            }
        }
        else if (ne.hasClass("comment")) {
            if (!node.name) {
                that.selectNode(ne);
                throw new Error("请填写备注信息");
            }

            /* 备注节点可以是孤立节点
            if (eids.indexOf(node.id) == -1) {
                that.selectNode(ne);
                throw new Error("备注节点不能是孤立节点");
            }
            */

            //备注节点没有名称
            node.remark = node.name;
            node.name = "";
            node.type = 0;
        }
        else {
            throw new Error("不支持的节点类型");
        }

        if (obj.data && obj.data.length > 0) {
            obj.data.forEach(function (d) {
                node.properties.push({ name: d.label, key: d.name, value: d.value, type: 1 });
            });
        }
        flow.nodes.push(node);
    });

    if (!sflag) {
        throw new Error("流程无开始节点");
    }

    if (!eflag) {
        throw new Error("流程无结束节点");
    }

    return flow;
}